package xyz.fivefaith.tool.common.util;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Expiry;

import org.apache.commons.lang3.tuple.Pair;
import org.checkerframework.checker.index.qual.NonNegative;

import java.time.Duration;
import java.util.Objects;
import java.util.function.Function;

/**
 * 本地缓存工具类
 *
 * @author zhaoanchi
 * @version 1.0
 * @since 1.0
 */
public class CaffeineTool<K, V> {
    private static final long TTL_MAX_SIZE = Duration.ofHours(1).toNanos();

    private final Cache<K, V> cache;

    private CaffeineTool(Cache<K, V> cache) {
        this.cache = cache;
    }

    /* -------------------- 永久缓存方法 -------------------- */

    public V get(K key, Function<K, V> loader) {
        return get(cache, key, loader);
    }

    public V getIfPresent(K key) {
        return getIfPresent(cache, key);
    }

    public void put(K key, V value) {
        put(cache, key, value);
    }

    public void remove(K key) {
        remove(cache, key);
    }

    public void clear() {
        clear(cache);
    }

    public long size() {
        return size(cache);
    }

    /* -------------------- 通用底层方法 -------------------- */

    private V get(Cache<K, V> cache, K key, Function<K, V> loader) {
        return cache.get(key, loader);
    }

    private V getIfPresent(Cache<K, V> cache, K key) {
        return cache.asMap().get(key);
    }

    private void put(Cache<K, V> cache, K key, V value) {
        cache.put(key, value);
    }

    private void remove(Cache<K, V> cache, K key) {
        cache.invalidate(key);
    }

    private void clear(Cache<K, V> cache) {
        cache.invalidateAll();
    }

    private long size(Cache<K, V> cache) {
        return cache.estimatedSize();
    }

    /* -------------------- 暴露实例（可选） -------------------- */

    public static <K, V> CaffeineTool<K, V> getInstance() {
        Cache<K, V> build = Caffeine.newBuilder().build();
        return new CaffeineTool<>(build);
    }

    public static <K, V> CaffeineTool<K, V> getInstance(int maximum) {
        Cache<K, V> build = Caffeine.newBuilder().maximumSize(maximum).build();
        return new CaffeineTool<>(build);
    }

    public static <K, V> CaffeineTool<K, Pair<V, Duration>> getTtInstance(int maximum) {
        Cache<K, Pair<V, Duration>> ttlCache =
                Caffeine.newBuilder()
                        .maximumSize(maximum)
                        .expireAfter(
                                new Expiry<K, Pair<V, Duration>>() {
                                    @Override
                                    public long expireAfterCreate(
                                            K key, Pair<V, Duration> value, long now) {
                                        if (Objects.isNull(value.getRight())) {
                                            return TTL_MAX_SIZE;
                                        }

                                        return value.getRight().toNanos();
                                    }

                                    @Override
                                    public long expireAfterUpdate(
                                            K key,
                                            Pair<V, Duration> value,
                                            long currentTime,
                                            @NonNegative long currentDuration) {
                                        return value.getRight().toNanos();
                                    }

                                    @Override
                                    public long expireAfterRead(
                                            K key,
                                            Pair<V, Duration> value,
                                            long currentTime,
                                            @NonNegative long currentDuration) {
                                        return currentDuration;
                                    }
                                })
                        .build();
        return new CaffeineTool<>(ttlCache);
    }
}
